Skip to content

feat(server): forge state-transition arms and actor memo (RIG-3331) - #1018

Open
rigel-mintaka wants to merge 1 commit into
compass-forge/rig-3331-providerfrom
compass-forge/rig-3331-server-arms
Open

feat(server): forge state-transition arms and actor memo (RIG-3331)#1018
rigel-mintaka wants to merge 1 commit into
compass-forge/rig-3331-providerfrom
compass-forge/rig-3331-server-arms

Conversation

@rigel-mintaka

@rigel-mintaka rigel-mintaka commented Sep 8, 2026

Copy link
Copy Markdown
Contributor

This PR is part of a stack containing 6 PRs:

  1. main
  2. feat(forge): forge state-transition wire arms and provider methods (RIG-3331) #1017
  3. "feat(server): forge state-transition arms and actor memo (RIG-3331)" (this PR)
  4. feat(agent): forge state-transition tools (RIG-3331) #1019
  5. feat(forge): carry the owner-qualified actor handle on the wire (RIG-3326) #1020
  6. feat(tools): mint the live-oracle Linear token with app:assignable (RIG-3299) #1021
  7. refactor(proto): standardize the PR-number wire field on pr_number (RIG-3561) #1037

Implements T4 of docs/designs/server/compass-forge-state-transition/design.md — the server arms and the actor memo. Stacked on #1017.

The arms

Two forgeService arms following the existing shape: resolveTarget, arm validation, author-client dispatch, mapForgeError flattening, updated canonical artifact through the existing translateIssue / translatePR.

Three validation screens, all before any provider touch:

  • State domainopen or closed; anything else (including merged and a Linear workflow-state name) is invalid_argument.
  • Refinement/providerclose_reason is GitHub-issues-only, workflow_state is Linear-only. Rejected HERE, keyed on the RESOLVED coordinate, which is why a provider may safely ignore a foreign refinement.
  • PR refinement — the PR arm accepts none at all.

No F3 dedup and no owner stamp on either arm: client_request_id is documented "ignored on non-create arms", and a transition has no body to stamp.

The memo (why it exists)

A transition has no body, so the owner header cannot attribute it, and every Server-credential write presents the shared App bot login. Durable server-side correlation is the only channel that can name WHICH agent drove a transition — that is what RIG-3331 OQ-1 ruled (2026-09-07), and it is what RIG-3326's STATE arm consumes.

forge_state_transitions lands in 0001_init.sql, not a new numbered migration: that directory holds exactly one migration by a standing ruling, and the same reasoning folds each later migration in as it accretes. It is coordinate-aligned to forge_authored_artifacts — same (tenant_id, forge_provider, forge_host, repo, kind, number) PK — so a re-transition re-lands on the key rather than accreting rows.

Load-bearing properties:

  • tenant_id is not incidental. Two tenants legitimately hold the same forge coordinate, so without it one tenant's memo could attribute another's STATE event. It rides the column DEFAULT + RLS, never a caller argument, matching the AuthoredArtifactByCoordinate precedent.
  • The consume is ONE statement (UPDATE … RETURNING), so claim and read are atomic and a concurrent second reader matches nothing.
  • The memo is written strictly AFTER provider success. Inverting it would attribute a STATE event to an agent whose write the forge refused.
  • A transition writes NO forge_authored_artifacts row — that row is write-once authorship whose DO UPDATE would destroy the original create's idempotency memo. Asserted explicitly.
  • The recorded state is the REQUESTED portable target, not the returned artifact's raw state: a merged PR reads back merged, outside both the notify lane's match domain and the table's CHECK.

One judgment call the record did not spell out

The upsert resets consumed_at to NULL. Without it, an agent that closes an issue (memo consumed) and later reopens it is permanently unattributable at that coordinate, contradicting "latest transition wins". It is behind its own named test so it can be reversed in one place.

Tests

13 unit tests in go/server over fake provider + fake store: dispatch on both arms, every validation rejection, memo written only after success, memo absent after failure, PR memo carries the portable state and PR kind. Includes a positive control — a screen that rejected EVERY refinement would pass the two rejection tests alone.

7 store pgtests, executed against real Postgres: upsert-latest-wins, upsert re-arms a consumed memo, consume-exactly-once, freshness bound (and its inclusivity), and one miss case per coordinate component.

The RLS coverage was proven load-bearing, not just written: removing the forge_state_transitions entry from the DO-loop turns TestRLSCatalogEnabledAndForced RED.

Ledger-impact: none — DL-342/DL-343 landed with the record's freeze in #981.

Review round 1 — resolved

Reviewed by the review agent over the whole stack (high 3, medium 7, low 6).
The core RIG-3331 mechanism (memo ordering, one-shot consume, tenant isolation,
provider methods, Linear resolution, error mapping, recorded-state choice) was
verified correct. All three highs were stack-integration regressions, now fixed:

  • Stale base / three generated-file conflicts — rebased the line onto current
    main; every conflict resolved by regenerating (buf + sqlc), never by
    hand-merging a generated file.
  • Silent RIG-2616 revert — the stack's generated code predated main's
    SessionError regen (45 -> 0 occurrences). The regen restores it: SessionError
    is back to 45 in go/gen/compass/v1/compass.pb.go and 14 in the agent TS,
    with ownerHandle and the transition arms additive on top.
  • Deleted approval-mode assertions (feat(agent): forge state-transition tools (RIG-3331) #1019) — restored the full approvalOf
    loop over all twelve tools (3 reads + 9 writes) with its justification comment,
    rather than the two-tool assertion that replaced it.

Mediums fixed: updated_at/created_at + updated_at_tables entry for
forge_state_transitions (main's RIG-3495 convention, which landed after this
branched) plus a sqlc regen; the single-column FK divergence documented; the
memo coordinate contract documented on rememberTransition; the memo-failure
error now names the forge write that landed; the Linear retry gate narrowed to
the actual staleness signal; workflow-state page truncation now fails loud at
422; the two transition schemas routed through the compassv1 barrel.

Both new provider tests were mutation-proved: widening the retry gate reddens
TestLinearTransitionDoesNotRetryOnNonStaleness200, and removing the truncation
guard reddens TestLinearTransitionRejectsTruncatedWorkflowStatePage.

Gate: moon ci 70 actions, 0 failed against MOON_BASE=origin/main.

The initial forge live-oracle 401s were mint contention, not a defect: the
Linear client_credentials app holds one active token, so five concurrent CI
runs each revoked the previous one's (which is why the last-to-mint PR was
green). Re-run serially, the job passes on every head with no code change.

@linear-code

linear-code Bot commented Sep 8, 2026

Copy link
Copy Markdown

RIG-3331

@github-actions

github-actions Bot commented Sep 8, 2026

Copy link
Copy Markdown

Compass engineering docs preview: https://compass-forge-rig-3331-serve.compass-eng-docs.pages.dev

Deployed from compass-forge/rig-3331-server-arms at 1b017a6.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant